#include <GL/freeglut.h>
#include <boost/thread.hpp>
#include <iostream>
#include <vector>
#include <Vector3D.h>
#include <string>

#include "Ball.h"
#include "Cue.h"
#include "CueGLPicker.h"
#include "GLCallbacks.h"
#include "GLTextures.h"
#include "GLTools.h"
#include "GravityRigidBodySimulation.h"
#include "Rectangle.h"
#include "TimeMeasure.h"

float width = 150;
float height = 150;
double y_height = 150;

GLuint ball_textures[16];
GLuint bricks_texture;
GLuint wood_texture;
GLuint green_texture;
GLuint yellow_texture;
GLuint black_texture;

/////////////////
// Simulation: //
/////////////////
coolpool::Simulation * simulation;
boost::thread * simulation_thread;
int32_t start_time;
int32_t frame_real_duration; // Time of the duration of one frame, rendering + simulation

// Counters for calculating average frame duration
int32_t count_real_duration = 0;
int32_t sum_real_duration = 0;
int32_t max_count = 5000;

bool first_round = true; // First run of the simulation
double fps = 30;
double frame_duration = 1000.0 / fps; // Frame duration in milliseconds.

/////////////
// Lights: //
/////////////
GLfloat spec[] =
{ 1.0, 1.0, 1.0, 1.0 }; //sets specular highlight of balls
GLfloat posl[] =
{ 0, 400, 0, 1 }; // position of light source
GLfloat amb[] =
{ 0.2f, 0.2f, 0.2f, 1.0f }; //global ambient
GLfloat amb2[] =
{ 0.3f, 0.3f, 0.3f, 1.0f }; //ambient of lightsource

//////////
// GUI: //
//////////
// The rotation around Y axis at the ball.
double rotateY = 0.0;

// Buffer for the rotation around Y axis.
int last_x = 0;

// Distance from the ball.
// Changes by scrolling
double distance_ = 150.0;

// Cue object.
coolpool::Cue * cue = NULL;

// Buffer for the moving function.
// Desides whether the cue is picked or not.
bool cue_picked = false;

// OpenGL name for the cue.
const GLuint CUE_NAME = 1;

// Camera setup, used only when the cue is not defined.
math_tools::Vector3D look_at_from;
math_tools::Vector3D look_at_to;
math_tools::Vector3D animate_look_at_from;
math_tools::Vector3D animate_look_at_to;

// OGL picker of the cue.
coolpool::CueGLPicker picker(CUE_NAME);

void onClose();

// Rendering:
void display();
void beforeDisplay();

// GUI:
// glutMouseFunc
void mouseClick(int button, int state, int x, int y);
void pickCue(int button, int x, int y);
void handleScrolling(int button);

// glutMotionFunc
void mouseMotion(int x, int y);
void rotateSceneAroundY(int x);

void loadTextures();
void setBallCoeffs1(coolpool::Ball * ball);
void setBallCoeffs2(coolpool::Ball * ball);
int randomNumber(int lowest, int highest);
void createTable(double width, double length, double height, std::vector<coolpool::Entity *> * entities, bool closed, GLuint side_texture);
void genBalls(double from_x, double to_x, double from_z, double to_z, double height, double radius, double dist, double mass, coolpool::EntitiesPointers * entities, int max_num,
		int set_ball_coefs_num);

void setupScene0(coolpool::EntitiesPointers * entities);
void setupScene1(coolpool::EntitiesPointers * entities);
void setupScene2(coolpool::EntitiesPointers * entities);
void setupScene3(coolpool::EntitiesPointers * entities);
void setupScene4(coolpool::EntitiesPointers * entities);
void setupScene5(coolpool::EntitiesPointers * entities);
void setupScene6(coolpool::EntitiesPointers * entities, int num_balls);

std::string	message = "\nHelp: First parameter is for number of worker threads which will be used for collision detection. \nThen use parameters 0 to 6 to run the demos,\nfor demo 6 there is an optional parameter which says the number of generated balls.\nExample: ./CoolPool 2 0";

int main(int argc, char *argv[])
{
	if (argc < 3) 
	{
		std::cout << "Error: Missing program parameters." << message << std::endl;
		exit(1);
	} 
	else if (argc > 4)
	{
		std::cout << "Error: Too many program parameters. " << message << std::endl;
		exit(1);
	}

	int num_workers = atoi(argv[1]); 
	if (num_workers <= 0)
	{
		std::cout << "Error: The program needs at least one worker." << message << std::endl; 
		exit(0);
	}

	srand((unsigned) time(0));
	glutInit(&argc, argv);
	glutInitWindowSize(800, 600);
	glutInitWindowPosition(100, 100);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutCreateWindow("CoolPool");
	glutMouseFunc(mouseClick);
	glutMotionFunc(mouseMotion);
	glutCloseFunc(onClose);
	glutReshapeFunc(resize);
	GLfloat values[2];
	glGetFloatv(GL_LINE_WIDTH_GRANULARITY, values);
	glGetFloatv(GL_LINE_WIDTH_RANGE, values);

	glEnable(GL_LINE_SMOOTH);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
	glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE);
	glLineWidth(1.5);
	glutDisplayFunc(display);
	glutKeyboardFunc(key);
	glutIdleFunc(idle);

	loadTextures();

	std::vector<coolpool::Entity *> entities;
	int count;
	switch (*argv[2])
	{
		case '0':
			setupScene0(&entities);
			break;
		case '1':
			setupScene1(&entities);
			break;
		case '2':
			setupScene2(&entities);
			break;
		case '3':
			setupScene3(&entities);
			break;
		case '4':
			setupScene4(&entities);
			break;
		case '5':
			setupScene5(&entities);
			break;
		case '6':
		{
			count = argc > 3 ? atoi(argv[3]) : 20;
			if (count < 1)
				count = 20;
			setupScene6(&entities, count);
			break;
		}
		default:
			std::cout << "Error: Wrong second program parameter. " << message << std::endl;
			exit(1);
			break;
	}

	simulation = new coolpool::GravityRigidBodySimulation(entities, num_workers, math_tools::Vector3D(0.0, -1.5, 0.0));
	simulation_thread = new boost::thread(&coolpool::Simulation::run, simulation);

	glutMainLoop();
}

void setupScene0(coolpool::EntitiesPointers * entities)
{
	rotateY = 180;
	distance_ = 250;
	double width = 200;
	double length = 400;
	double height = 30;

	createTable(width, length, height, entities, false, wood_texture);

	coolpool::Rectangle * rec = new coolpool::Rectangle(math_tools::Vector3D(0 - 500, -5.0, 0 - 500), math_tools::Vector3D(width + 500, -5.0, 0 - 500),
			math_tools::Vector3D(width + 500, -5.0, length + 500), math_tools::Vector3D(0, -5.0, length + 500));
	rec->setTexture(black_texture);
	entities->push_back(rec);

	double ballRadius = 10.0;
	coolpool::Ball * ball = new coolpool::Ball(ballRadius);

	ball->setCenterOfMassLocation(math_tools::Vector3D(width / 2, ballRadius + 1, length * 1 / 4));
	ball->setTexture(ball_textures[0]);
	setBallCoeffs1(ball);
	entities->push_back(ball);
	cue = new coolpool::Cue(ball, 20, 80, 0.8, yellow_texture);

	double start = length * 2 / 3;
	int count = 1;
	double move = ballRadius * 2 + 1;
	int i = 1;
	int line = 0;
	while (i < 16)
	{
		double tmp = width / 2 - count * ballRadius;
		for (int j = 0; j < count; ++j)
		{
			ball = new coolpool::Ball(ballRadius);
			setBallCoeffs1(ball);
			ball->setTexture(ball_textures[i]);
			ball->setCenterOfMassLocation(Vector3D(tmp, ballRadius + 1, line * move + start));
			entities->push_back(ball);
			tmp += ballRadius * 2 + 1;
			++i;
		}
		line++;
		count++;
	}
}

void setupScene1(coolpool::EntitiesPointers * entities)
{
	distance_ = 250;
	double width = 200;
	double length = 200;
	double height = 40;
	look_at_to = math_tools::Vector3D(width / 2.0, 0.0, length / 2.0);
	look_at_from = math_tools::Vector3D(width / 2.0, 50.0, length / 2.0);

	createTable(width, length, height, entities, false, wood_texture);

	coolpool::Ball * ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(width / 2.0, 40.0, length / 2.0));
	setBallCoeffs1(ball);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setTexture(ball_textures[1]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(width / 2.0, 80.0, length / 2.0));
	setBallCoeffs1(ball);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setTexture(ball_textures[2]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(width / 2.0, 120.0, length / 2.0));
	setBallCoeffs1(ball);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setTexture(ball_textures[3]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(width / 2.0, 140.0, length / 2.0));
	setBallCoeffs1(ball);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setTexture(ball_textures[4]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(width / 2.0, 180.0, length / 2.0));
	setBallCoeffs1(ball);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setTexture(ball_textures[6]);
	entities->push_back(ball);
}

void setupScene2(coolpool::EntitiesPointers * entities)
{
	distance_ = 350;
	double width = 400;
	double length = 200;
	double height = 30;
	rotateY = -25;
	look_at_to = math_tools::Vector3D(width / 2.0, 0.0, length / 2.0);
	look_at_from = math_tools::Vector3D(width / 2.0, 50.0, length / 2.0);

	createTable(width, length, height, entities, false, wood_texture);

	coolpool::Ball * ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(100, 300, 60));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[1]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(100, 300, 80));
	ball->setAngularVelocity(math_tools::Vector3D(0, 0, -15));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[2]);
	entities->push_back(ball);
}

void setupScene3(coolpool::EntitiesPointers * entities)
{
	distance_ = 450;
	double width = 250;
	double length = 200;
	double height = 60;
	look_at_to = math_tools::Vector3D(width / 2.0, 1.0, length / 2.0);
	look_at_from = math_tools::Vector3D(width / 2.0, 120.0, length / 2.0);
	animate_look_at_from = math_tools::Vector3D(8.0, 0.0, 0.0);
	animate_look_at_to = animate_look_at_from;

	coolpool::Rectangle * rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, 0), math_tools::Vector3D(width, 0, 0), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(0, 0, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, 0), math_tools::Vector3D(0, 0, length), math_tools::Vector3D(0, height, length), math_tools::Vector3D(0, height, 0));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, height, 0), math_tools::Vector3D(width, height, 0), math_tools::Vector3D(width, 0, 0), math_tools::Vector3D(0, 0, 0));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, length), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(width, height, length), math_tools::Vector3D(0, height, length));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	double move = 600;
	rec = new coolpool::Rectangle(math_tools::Vector3D(0 + move, 0, 0), math_tools::Vector3D(width + move, 0, 0), math_tools::Vector3D(width + move, 0, length),
			math_tools::Vector3D(0 + move, 0, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0 + move, 0, 0), math_tools::Vector3D(0 + move, 0, length), math_tools::Vector3D(0 + move, height, length),
			math_tools::Vector3D(0 + move, height, 0));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0 + move, height, 0), math_tools::Vector3D(width + move, height, 0), math_tools::Vector3D(width + move, 0, 0),
			math_tools::Vector3D(0 + move, 0, 0));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width + move, height, 0), math_tools::Vector3D(width + move, height, length), math_tools::Vector3D(width + move, 0, length),
			math_tools::Vector3D(width + move, 0, 0));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0 + move, 0, length), math_tools::Vector3D(width + move, 0, length), math_tools::Vector3D(width + move, height, length),
			math_tools::Vector3D(0 + move, height, length));
	rec->setTexture(wood_texture);
	entities->push_back(rec);

	double ramp_height = 70;
	move = 250;
	rec = new coolpool::Rectangle(math_tools::Vector3D(0 + move, 0, 0), math_tools::Vector3D(width + move - 100, ramp_height, 0), math_tools::Vector3D(width + move - 100, ramp_height, length),
			math_tools::Vector3D(0 + move, 0, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	coolpool::Ball * ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(125, 20, 80));
	ball->setLinearVelocity(math_tools::Vector3D(100, 0, 0));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[2]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(80, 20, 100));
	ball->setLinearVelocity(math_tools::Vector3D(100, 0, 0));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[9]);
	entities->push_back(ball);

	ball = new coolpool::Ball(10);
	ball->setCenterOfMassLocation(math_tools::Vector3D(50, 20, 100));
	ball->setLinearVelocity(math_tools::Vector3D(100, 0, 0));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[6]);
	entities->push_back(ball);

	ball = new coolpool::Ball(15);
	ball->setMass(0.5);
	ball->setCenterOfMassLocation(math_tools::Vector3D(150, 20, 130));
	ball->setLinearVelocity(math_tools::Vector3D(160, 0, 0));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[4]);
	entities->push_back(ball);

	ball = new coolpool::Ball(15);
	ball->setMass(0.5);
	ball->setCenterOfMassLocation(math_tools::Vector3D(70, 20, 150));
	ball->setLinearVelocity(math_tools::Vector3D(145, 0, 0));
	setBallCoeffs1(ball);
	ball->setTexture(ball_textures[13]);
	entities->push_back(ball);
}

void setupScene4(coolpool::EntitiesPointers * entities)
{
	distance_ = 350;
	double width = 200;
	double length = 200;
	double height = 60;
	look_at_to = math_tools::Vector3D(width / 2.0, 0.0, length / 2.0);
	look_at_from = math_tools::Vector3D(width / 2.0, 180.0, length / 2.0);
	double scew = 150.0;
	double scew_height = 70;
	createTable(width, length, height, entities, false, wood_texture);

	coolpool::Rectangle * rec;
	rec = new coolpool::Rectangle(math_tools::Vector3D(0 - scew, height + scew_height, 0), math_tools::Vector3D(0, height, 0), math_tools::Vector3D(0, height, length),
			math_tools::Vector3D(0 - scew, height + scew_height, length));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, height + scew_height, 0 - scew), math_tools::Vector3D(width, height + scew_height, 0 - scew), math_tools::Vector3D(width, height, 0),
			math_tools::Vector3D(0, height, 0));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width + scew, height + scew_height, 0), math_tools::Vector3D(width + scew, height + scew_height, length),
			math_tools::Vector3D(width, height, length), math_tools::Vector3D(width, height, 0));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	genBalls(80, width - 80, -length / 2.0 + 80, 20, 150, 10, 10, 1, entities, -1, 1);
	genBalls(90, width - 90, -length / 2.0 + 80, 20, 190, 10, 10, 1, entities, -1, 1);

	genBalls(-width / 2.0 - 30, -80, 80, length / 2.0 + 30, 150, 15, 10, 0.5, entities, -1, 1);
	genBalls(-width / 2.0 - 40, -70, 90, length / 2.0 + 30, 190, 15, 10, 0.5, entities, -1, 1);

	genBalls(width + 40, width + width / 2.0 + 30, 80, length / 2.0 + 50, 150, 20, 10, 0.3, entities, -1, 1);
}

void setupScene5(coolpool::EntitiesPointers * entities)
{
	distance_ = 350;
	double width = 400;
	double length = 200;
	double height = 450;
	animate_look_at_from = math_tools::Vector3D(0.0, -0.8, 0.0);
	animate_look_at_to = animate_look_at_from;
	look_at_to = math_tools::Vector3D(width / 2.0, 250.0, length / 2.0);
	look_at_from = math_tools::Vector3D(width / 2.0, 580.0, length / 2.0);
	createTable(width, length, height, entities, false, bricks_texture);

	double level1 = 200;
	double scew = 100;

	coolpool::Rectangle * rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0 + level1, 0), math_tools::Vector3D(width - 70, 0 + level1 - scew, 0),
			math_tools::Vector3D(width - 70, 0 + level1 - scew, length), math_tools::Vector3D(0, 0 + level1, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	double level2 = level1 + 180;
	rec = new coolpool::Rectangle(math_tools::Vector3D(70.0, 0 + level2 - scew, 0), math_tools::Vector3D(width, 0 + level2, 0), math_tools::Vector3D(width, 0 + level2, length),
			math_tools::Vector3D(70.0, 0 + level2 - scew, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	genBalls(80, width - 80, 80, length - 80, 450, 10, 15, 1, entities, -1, 1);
	genBalls(70, width - 80, 80, length - 80, 470, 10, 20, 1, entities, -1, 1);
}

void setupScene6(coolpool::EntitiesPointers * entities, int num_balls)
{
	distance_ = 480;
	rotateY = 90.0;
	double width = 400;
	double length = 400;
	double height = 70;
	look_at_to = math_tools::Vector3D(width / 2.0, 80.0, length / 2.0);
	look_at_from = math_tools::Vector3D(width / 2.0, 280.0, length / 2.0);

	double scew_width = 200;
	double scew_height = 70;
	coolpool::Rectangle * rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, 0), math_tools::Vector3D(width, 0, 0), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(0, 0, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, 0), math_tools::Vector3D(0, 0, length), math_tools::Vector3D(0, height + 3 * scew_height, length),
			math_tools::Vector3D(0, height + 3 * scew_height, 0));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, height + 3 * scew_height, 0), math_tools::Vector3D(width + 3 * scew_width, height + 3 * scew_height, 0),
			math_tools::Vector3D(width + 3 * scew_width, 0, 0), math_tools::Vector3D(0, 0, 0));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width, height, 0), math_tools::Vector3D(width, height, length), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(width, 0, 0));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width + 3 * scew_width, height + 3 * scew_height, 0), math_tools::Vector3D(width + 3 * scew_width, height + 3 * scew_height, length),
			math_tools::Vector3D(width + 3 * scew_width, 0, length), math_tools::Vector3D(width + 3 * scew_width, 0, 0));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, length), math_tools::Vector3D(width + 3 * scew_width, 0, length),
			math_tools::Vector3D(width + 3 * scew_width, height + 3 * scew_height, length), math_tools::Vector3D(0, height + 3 * scew_height, length));
	rec->setTexture(bricks_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width + scew_width, height + scew_height, length), math_tools::Vector3D(width, height, length), math_tools::Vector3D(width, height, 0),
			math_tools::Vector3D(width + scew_width, height + scew_height, 0));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width + scew_width * 2, height + scew_height, length), math_tools::Vector3D(width + scew_width, height + scew_height, length),
			math_tools::Vector3D(width + scew_width, height + scew_height, 0), math_tools::Vector3D(width + scew_width * 2, height + scew_height, 0));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width + scew_width * 3, height + scew_height * 2, length), math_tools::Vector3D(width + scew_width * 2, height + scew_height, length),
			math_tools::Vector3D(width + scew_width * 2, height + scew_height, 0), math_tools::Vector3D(width + scew_width * 3, height + scew_height * 2, 0));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	int max_num_in_row = 10;
	int curr_num_in_row;
	int i = 0;
	while (num_balls > 0)
	{
		curr_num_in_row = num_balls > max_num_in_row ? max_num_in_row : num_balls;
		num_balls -= curr_num_in_row;
		genBalls(4.5 * scew_width + 5, 5.5 * scew_width, 20, length - 20, 4 * scew_height + (i++) * 20, 10, 15, 1, entities, curr_num_in_row, 2);
	}
}

void setBallCoeffs1(coolpool::Ball * ball)
{
	ball->setCoefficientOfRestitutionFor(0.38, coolpool::CP_REC);
	ball->setCoefficientOfStaticFrictionFor(0.4, coolpool::CP_REC);
	ball->setCoefficientOfDynamicFrictionFor(0.5, coolpool::CP_REC);
	ball->setCoefficientOfRollingFrictionFor(0.2, coolpool::CP_REC);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setCoefficientOfStaticFrictionFor(0.01, coolpool::CP_BALL);
	ball->setCoefficientOfDynamicFrictionFor(0.001, coolpool::CP_BALL);
}

void setBallCoeffs2(coolpool::Ball * ball)
{
	ball->setCoefficientOfRestitutionFor(0.38, coolpool::CP_REC);
	ball->setCoefficientOfStaticFrictionFor(0.3, coolpool::CP_REC);
	ball->setCoefficientOfDynamicFrictionFor(0.4, coolpool::CP_REC);
	ball->setCoefficientOfRollingFrictionFor(0.2, coolpool::CP_REC);
	ball->setCoefficientOfRestitutionFor(0.8, coolpool::CP_BALL);
	ball->setCoefficientOfStaticFrictionFor(0.0, coolpool::CP_BALL);
	ball->setCoefficientOfDynamicFrictionFor(0.0, coolpool::CP_BALL);
}

int randomNumber(int lowest, int highest)
{
	int range = (highest - lowest) + 1;
	return (lowest + int(range * rand() / (RAND_MAX + 1.0)));
}

void createTable(double width, double length, double height, std::vector<coolpool::Entity *> * entities, bool closed, GLuint side_texture)
{
	coolpool::Rectangle * rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, 0), math_tools::Vector3D(width, 0, 0), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(0, 0, length));
	rec->setTexture(green_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, 0), math_tools::Vector3D(0, 0, length), math_tools::Vector3D(0, height, length), math_tools::Vector3D(0, height, 0));
	rec->setTexture(side_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, height, 0), math_tools::Vector3D(width, height, 0), math_tools::Vector3D(width, 0, 0), math_tools::Vector3D(0, 0, 0));
	rec->setTexture(side_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(width, height, 0), math_tools::Vector3D(width, height, length), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(width, 0, 0));
	rec->setTexture(side_texture);
	entities->push_back(rec);

	rec = new coolpool::Rectangle(math_tools::Vector3D(0, 0, length), math_tools::Vector3D(width, 0, length), math_tools::Vector3D(width, height, length), math_tools::Vector3D(0, height, length));
	rec->setTexture(side_texture);
	entities->push_back(rec);

	if (closed)
	{
		rec = new coolpool::Rectangle(math_tools::Vector3D(width, height, length), math_tools::Vector3D(width, height, 0), math_tools::Vector3D(0, height, 0), math_tools::Vector3D(0, height, length));
		rec->setTexture(green_texture);
		entities->push_back(rec);
	}
}

void genBalls(double from_x, double to_x, double from_z, double to_z, double height, double radius, double dist, double mass, coolpool::EntitiesPointers * entities, int max_num,
		int set_ball_coefs_num)
{
	coolpool::Ball * ball;
	int counter = 0;
	for (double x_it = from_x; x_it < to_x; x_it += 2 * radius + dist)
	{
		for (double z_it = from_z; z_it < to_z; z_it += 2 * radius + dist)
		{
			if (counter++ >= max_num && max_num > 0)
				return;
			ball = new coolpool::Ball(radius);
			ball->setMass(mass);
			ball->setCenterOfMassLocation(math_tools::Vector3D(x_it + (rand() % 5), height + (rand() % 5), z_it + (rand() % 5)));
			ball->setTexture(ball_textures[abs(rand()) % 15]);

			switch (set_ball_coefs_num)
			{
				case 1:
					setBallCoeffs1(ball);
				case 2:
					setBallCoeffs2(ball);
					break;
				default:
					break;
			}
			entities->push_back(ball);
		}
	}
}

void loadTextures()
{
	std::stringstream path;
	for (int i = 0; i < 16; ++i)
	{
		path.str("");
		path << "data/textures/Pool_Ball_Skins/Ball" << i << ".bmp";
		ball_textures[i] = glLoadTexture(path.str().c_str());
	}

	bricks_texture = glLoadTexture("data/textures/bricks.bmp");
	wood_texture = glLoadTexture("data/textures/wood.bmp");
	green_texture = glLoadTexture("data/textures/green.bmp");
	yellow_texture = glLoadTexture("data/textures/yellow_glass.bmp");
	black_texture = glLoadTexture("data/textures/black.bmp");
}

//////////////////////////
// GL Callbacks and GUI //
//////////////////////////
void onClose()
{
	std::cout << "Average frame duration: "<< ((double)sum_real_duration) /((double)count_real_duration) << " ms" << std::endl;
	{
		boost::mutex::scoped_lock sc_lock(*simulation->getLock());

		simulation->kill();
		simulation->getCondition()->notify_one();
	}

	simulation_thread->join();
	delete simulation_thread;
	delete simulation;
	delete cue;

	glDeleteTextures(16, &ball_textures[0]);
	glDeleteTextures(1, &bricks_texture);
	glDeleteTextures(1, &wood_texture);
	glDeleteTextures(1, &green_texture);
	glDeleteTextures(1, &yellow_texture);
	glDeleteTextures(1, &black_texture);
}

// MOUSE CLICK:
void mouseClick(int button, int state, int x, int y)
{
	handleScrolling(button);
	pickCue(button, x, y);

	if (button == MOUSE_LEFT_BUTTON)
	{
		if (state == GLUT_DOWN)
		{
			cue_picked = picker.wasPicked();
			picker.resetWasPicked();
		}
		else if (state == GLUT_UP)
		{
			if (cue_picked)
			{
				cue->hit();
			}
			cue_picked = false;
		}
	}
	last_x = 0;
}

void pickCue(int button, int x, int y)
{
	handleScrolling(button);
	picker.startPicking(x, y);
	glInitNames();
	glPushName(CUE_NAME);
	if (cue)
		cue->render();
	glPopName();
	picker.stopPicking();
}

void handleScrolling(int button)
{
	if (button == MOUSE_SCROLL_UP)
	{
		distance_ -= 2.0;
	}
	else if (button == MOUSE_SCROLL_DOWN)
	{
		distance_ += 2.0;
	}
}

// MOUSE MOTION:
void mouseMotion(int x, int y)
{
	if (cue_picked)
	{
		math_tools::Vector3D location = GetOGLPos(x, y);
		cue->updatePosition(location);
	}
	else
	{
		rotateSceneAroundY(x);
	}
}

void rotateSceneAroundY(int x)
{
	if (last_x != 0)
	{
		rotateY += (x - last_x) * 0.1;
	}
	last_x = x;
}

void beforeDisplay()
{
	// Reset buffers and matrices:
	glEnable(GL_DEPTH_TEST);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Setup light:
	glEnable(GL_LIGHTING); // zapnutie a nastavenie svetla
	glLightfv(GL_LIGHT0, GL_POSITION, posl);
	glLightfv(GL_LIGHT0, GL_AMBIENT, amb2);
	glEnable(GL_LIGHT0);
	glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb);
	glEnable(GL_COLOR_MATERIAL);
}

void display()
{
	beforeDisplay();

	math_tools::Vector3D location;
	if (cue)
	{
		location = cue->getLookAtPosition();
		gluLookAt(location.getX(), 150, location.getZ() + distance_, location.getX(), 0, location.getZ(), 0, 1.0, 0.0);
	}
	else
	{
		location = look_at_to;
		if (look_at_to.getY() > 0.0 && look_at_to.getX() < 725)
		{
			look_at_to += animate_look_at_to;
			look_at_from += animate_look_at_from;
		}
		gluLookAt(look_at_from.getX(), look_at_from.getY(), look_at_from.getZ() + distance_, look_at_to.getX(), look_at_to.getY(), look_at_to.getZ(), 0, 1.0, 0.0);
	}

	glTranslated(location.getX(), 0, location.getZ());
	glRotated(rotateY, 0, 1, 0);
	glTranslated(-location.getX(), 0, -location.getZ());
	std::vector<coolpool::IRenderable *> * renderables;

	int32_t ellapsed_time = getMilliSpan(start_time);
	if (!first_round && count_real_duration < max_count)
	{
		sum_real_duration += getMilliSpan(frame_real_duration);
	}
	else
	{
		first_round = false;
	}
	if (count_real_duration < max_count)
		count_real_duration++;
	// Force fps:
	if (frame_duration - ellapsed_time > 0)
		boost::this_thread::sleep(boost::posix_time::milliseconds(frame_duration - ellapsed_time));
	start_time = getMilliCount();
	frame_real_duration = getMilliCount();
	Vector3D main_bal_loc;
	{
		boost::mutex::scoped_lock sc_lock(*simulation->getLock());
		// Get renderables.
		renderables = simulation->getRenderables();
		simulation->getCondition()->notify_one();
	}

	if (cue)
		cue->render();

	for (unsigned i = 0; i < renderables->size(); ++i)
	{
		(*renderables)[i]->render();

		// Delete the renderable objects.
		delete (*renderables)[i];
	}

	// Delete the temporary renderable container.
	delete renderables;

	glutSwapBuffers();
}
